/****

    activequant - activestocks.eu

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

	
	contact  : contact@activestocks.eu
    homepage : http://www.activestocks.eu

****/
package org.activequant.core.domainmodel.data;

import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

import org.activequant.core.domainmodel.InstrumentSpecification;
import org.activequant.core.domainmodel.SeriesSpecification;
import org.activequant.core.types.TimeStamp;
import org.activequant.util.exceptions.ValueNotFoundException;
import org.activequant.util.tools.ArrayUtils;

/**
 * Series base class which handles a set of elements and the according specification.<br>
 * Each element within the collection is unique, cannot be stored twice and belongs<br>
 * to this collection.<br/>
 * An abstract class, ArrayList subclass. Holds the following associated variables:
 * <ul>
 * <li>id(Long)</li>
 * <li>seriesSpecification(SeriesSpecification)</li>
 * <li>inherited list of elements (each of them a MarketDataEntity subclass)</li>
 * </ul>
 * <br/>
 * <b>History:</b><br>
 *  - [16.06.2007] Created (Ulrich Staudinger)<br>
 *  - [16.06.2007] Added some generic functionality (Erik Nijkamp)<br>
 *  - [23.06.2007] moved to new domain model (Erik Nijkamp)<br>
 *  - [14.08.2007] refactored contractSpec to seriesSpecification (Ulrich Staudinger)<br>
 *  - [27.09.2007] @JoinColumn + removed array mess (Erik Nijkamp)<br>
 *  - [28.09.2007] added generic functionality (Erik Nijkamp)<br>
 *  - [19.11.2007] Adding apply series specification method (Ulrich Staudinger)<br>
 *  - [02.03.2008] Moved dirty sublist code to TimeSeriesUtils (Erik Nijkamp)<br>
 *
 *  @author Ulrich Staudinger
 *  @author Erik Nijkamp
 */
public abstract class TimeSeries<T extends MarketDataEntity<T>> extends ArrayList<T> {	
	/**
	 * protected Long id;
	 */
	protected Long id;
	/**
	 * protected SeriesSpecification seriesSpecification;
	 */
	protected SeriesSpecification seriesSpecification;
	
	public abstract TimeSeries<T> clone();
	/**
	 * empty constructor
	 */
	protected TimeSeries() {
		
	}
	/**
	 * constructs a TimeSeries using the given seriesSpecification(SeriesSpecification) to set its associated seriesSpecification(SeriesSpecification)
	 * @param seriesSpecification
	 */
	protected TimeSeries(SeriesSpecification seriesSpecification) {
		this.seriesSpecification = seriesSpecification;
	}
	/**
	 * constructs a TimeSeries(an ArrayList subclass) using the given seriesData(List&lt;T&gt;) as its elements
	 * @param seriesData
	 */
	protected TimeSeries(List<T> seriesData) {
		super(seriesData);
	}
	
	/**
	 * returns the associated id(Long)
	 * @return the id
	 */    
	public Long getId() {
		return id;
	}

	/**
	 * sets the associated id(Long) with the given id(Long)
	 * @param id the id to set
	 */
	public void setId(Long id) {
		this.id = id;
	}
	
	/**
	 * returns the name(String) of the symbol(Symbol) of the instrumentSpecification(InstrumentSpecification) of the associated seriesSpecification(SeriesSpecification)<br/>
	 * return the symbol name of the associated instrument
	 * @return
	 */
	public String getSymbolName() {
		return getInstrumentSpecification().getSymbol().toString();
	}
	
// ############### Specification ###############
	
	/**
	 * Returns <code>seriesSpecification.toString()</code>, which is a string in the form "${id}\t${symbol}\t${exchange}\t${vendor}\t${currency}\t${securityType}\t${expiry}\t${contractRight}\t${strike} \t ${startTimeStamp} \t ${endTimeStamp}"<br/>
	 * returns the name of this series
	 * @return the name
	 */
	public String getName() {
		return seriesSpecification.toString();
	}
	
	/**
	 * returns whether the associated seriesSpecification(SeriesSpecification) is not <code>null</code><br/>
	 * returns true if an instrument specification is associated
	 * @return
	 */
	public boolean hasName() {
		return seriesSpecification != null;
	}
	
	/**
	 * applies a given instrument specification to all elements of this list<br/>
	 * <strong>1.</strong> sets the instrumentSpecification(InstrumentSpecification) of the associated seriesSpecification(SeriesSpecification) with the given instrumentSpecification(InstrumentSpecification)<br/>
	 * <strong>2.</strong> sets the instrumentSpecification(InstrumentSpecification) of each of the elements (MarketDataEntity subclasses) of this TimeSeries(an ArrayList subclass) with the given instrumentSpecification(InstrumentSpecification)
	 * @param instrumentSpecification
	 */
	public void applyInstrumentSpecification(InstrumentSpecification instrumentSpecification) {
		assert(instrumentSpecification != null);
		seriesSpecification.setInstrumentSpecification(instrumentSpecification);
		for(T object : this) {
			object.setInstrumentSpecification(instrumentSpecification);
		}
	}

	/**
	 * applies a given series specification to the series itself and all elements<br/>
	 * <strong>1.</strong> sets the associated seriesSpecification(SeriesSpecification) with the given seriesSpecification(SeriesSpecification)<br/>
	 * <strong>2.</strong> sets the instrumentSpecification(InstrumentSpecification) of each of the elements (MarketDataEntity subclasses) of this TimeSeries(an ArrayList subclass) 
	 * with the instrumentSpecification(InstrumentSpecification) of the given seriesSpecification(SeriesSpecification)
	 * @param seriesSpecification
	 */
	public void applySeriesSpecification(SeriesSpecification seriesSpecification) {
		assert(seriesSpecification != null);
		this.seriesSpecification = seriesSpecification;
		for(T object : this) {
			object.setInstrumentSpecification(seriesSpecification.getInstrumentSpecification());
		}
	}
	/**
	 * returns the associated seriesSpecification(SeriesSpecification)
	 * @return
	 */
	public SeriesSpecification getSeriesSpecification() {
		return seriesSpecification;
	}
	/**
	 * sets the associated seriesSpecification(SeriesSpecification) with the given seriesSpecification(SeriesSpecification)
	 * @param seriesSpecification
	 */
	public void setSeriesSpecification(SeriesSpecification seriesSpecification) {
		this.seriesSpecification = seriesSpecification;
	}
	/**
	 * returns the instrumentSpecification(InstrumentSpecification) of the associated seriesSpecification(SeriesSpecification)
	 * @return
	 */
	public InstrumentSpecification getInstrumentSpecification() {
		assert(seriesSpecification != null);
		return seriesSpecification.getInstrumentSpecification();
	}
	
// ############### List operations ###############
	
	/**
	 * generic method which allows to set the <T> entities directly<br/>
	 * sets the elements of this TimeSeries(an ArrayList subclass) with the given entities(T...)
	 * @param entities
	 */
	public void setEntities(T... entities) {
		this.clear();
		this.addAll(ArrayUtils.asList(entities));
	}
	
	/**
	 * returns a cloned sub-list
	 * @param start
	 * @param end
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public TimeSeries<T> clonedSubList(int start, int end) {
		assert(start >= 0 && end <= size() && start < end) : "start < 0 || end > size() || start > end";
		TimeSeries<T> clone = (TimeSeries<T>) clone();
		java.util.List<T> subList = super.subList(start, end);
		clone.clear();
		clone.addAll(subList);
		return clone;
	}
	
	/**
	 * returns the first element of this TimeSeries(an ArrayList subclass)
	 * @return
	 */
	public T firstElement() {
		assert(!this.isEmpty());
		return get(0);
	}
	/**
	 * returns the last element of this TimeSeries(ArrayList subclass)
	 * @return
	 */
	public T lastElement() {
		assert(!this.isEmpty());
		return this.get(size()-1);
	}
	
    
    /**
     * returns the position of the given date within the array<br/>
     * iterates the elements of this TimeSeries(ArrayList subclass) and returns the position of the element whose timeStamp(TimeStamp) matches the given date(TimeStamp)
     * @param date
     * @return
     * @throws ValueNotFoundException
     */
	public int getTimeStampPosition(TimeStamp date) throws ValueNotFoundException {
		ListIterator<T> iter = this.listIterator();
		while (iter.hasNext()) {
			if (iter.next().getTimeStamp().equals(date))
				return iter.previousIndex();
		}
		throw new ValueNotFoundException("Cannot find value for date '" + date + "'.");
	}
	/**
	 * returns a TimeStamp[] containing the TimeStamp of every one of the elements of this TimeSeries(ArrayList subclass)
	 * @return
	 */
    public TimeStamp[] getTimeStamps() {
        TimeStamp[] dates = new TimeStamp[size()];
        int idx = 0;
        for (T element : this) {
        	dates[idx++] = element.getTimeStamp();
        }
        return dates;
    }
    /**
     * sets the TimeStamps of the elements of this TimeSeries(ArrayList subclass) with the given dates(TimeStamp[])
     * @param dates
     */
    public void setTimeStamps(TimeStamp[] dates) {
        int idx = 0;
        for(T element : this) {
        	element.setTimeStamp(dates[idx++]);
        }
    }
    /**
     * returns the element whose timeStamp(TimeStamp) matches the given date(TimeStamp)
     * @param date
     * @return
     * @throws ValueNotFoundException
     */
	public T getByTimeStamp(TimeStamp date) throws ValueNotFoundException {
		return get(getTimeStampPosition(date));
	}
	/**
	 * removes the element whose timeStamp(TimeStamp) matches the given date(TimeStamp)
	 * @param date
	 * @throws ValueNotFoundException
	 */
	public void removeByTimeStamp(TimeStamp date) throws ValueNotFoundException {
		remove(getByTimeStamp(date));
	}
    
	/**
	 * returns whether there is an element whose timeStamp(TimeStamp) matches the given date(TimeStamp)
	 * @param date
	 * @return
	 */
	public boolean containsTimeStamp(TimeStamp date) {
		for(TimeStamp checkDate: getTimeStamps()) {
			if(checkDate.isEqual(date))
				return true;
		}
		return false;
	}	

    /**
     * method to fetch a subset / timeframe of this series.<br/>
     * returns the elements whose TimeStamps are within(inclusive) the given start-end(TimeStamp) interval
     * @param start
     * @param end
     * @return
     */
    public List<T> getTimeFrame(TimeStamp start, TimeStamp end) {
    	if(!start.isBefore(end)) {
    		throw new IllegalArgumentException("start must be before end: start=" + start + ", end=" + end);
    	}
    	List<T> list = clone();
    	list.clear();
        for (T t : this) {
        	if ((t.getTimeStamp().isAfter(start) && t.getTimeStamp().isBefore(end))
            	|| t.getTimeStamp().equals(start) || t.getTimeStamp().equals(end) ) {
            	list.add(t);
            }
        }
        return list;
    }
	
    /**
     * returns a sublist for the given positions.
	 * @param start
	 * @param end
	 * @return
     */
    public List<T> subList(int start, int end) {
    	// note: endPos > startPos 
		assert(start >= 0 && end <= size() && start < end) : "start < 0 || end > size() || start > end";
		List<T> list = clone();
		list.clear();
		List<T> subList = super.subList(start, end);
		list.addAll(subList);
		return list;
	}
	
	/**
	 * returns a sublist for the given period of time.
	 * @param start
	 * @param end
	 * @return
	 * @throws ValueNotFoundException
	 */
	public List<T> subList(TimeStamp start, TimeStamp end) throws ValueNotFoundException {
		// note: startPos > endPos
		assert(start.isBefore(end));
		int startPos = getTimeStampPosition(start);
		int endPos = getTimeStampPosition(end);
		return subList(endPos, startPos);
	}
}